﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace SW2A311A
{
    class StockCollection : IEnumerable      // IEnumerable implementeres så vi kan bruge Enumerator typer.
    {
        #region Instansvariabler
        private List<ProductCounter> _wrappedList;
        public event EventHandler<StockCollectionArgs> OnStockChange = delegate { };      // OnStockChange EventHandler delegate erklæres.
        #endregion

        #region Constructor
        // Instantieres med minimum-værdierne til alle produkt-typerne.
        public StockCollection(int hoodMinimum, int ventingDryerMinimum,
            int condensingDryerMinimum, int microwaveOvenMinimum, int builtInOvenMinimum)
        {
            StockObserver observer = new StockObserver(hoodMinimum, ventingDryerMinimum,    // Der laves en instans af StockObserver i constructoren, 
                condensingDryerMinimum, microwaveOvenMinimum, builtInOvenMinimum);          // så der er en StockObserver til alle instanser af StockCollection.

            observer.Subscribe(this);                                                       // Observer sættes til at abbonerer på EventHandleren i StockCollection-objektet.

            _wrappedList = new List<ProductCounter>();                                      // _wrappedList instantieres, så den er klar til at modtage Products.
        }
        #endregion

        #region Metoder
        #region Add/Remove metoder
        internal void Add(Product item)
        {
            Add(item, 1);                                             // Genbruger Add metoden der modtager Product og Antal.
        }

        internal void Add(Product item, int number)
        {
            bool productExists = false;                               // Værdi til at tjekke om der Product'et allerede eksisterer i list'en.
            foreach (ProductCounter p in _wrappedList)
                if (p.Product.Equals(item))                           // Bruger vores Equals override metode til at teste for værdi-mæssig lighed.
                {
                    productExists = true;
                    p.NumberOfProduct += number;                      // Tilføjer nye Products til list'en.
                    OnStockChanged(new StockCollectionArgs(p));       // Starter vores OnStockChanged event. Og sender den nye mængde af Product'et med som Argument.
                }
            if (!productExists)                                       // Hvis Product'et ikke eksistere skal det genereres i list'en.
            {
                ProductCounter tempProductCounter = new ProductCounter(item, number);
                _wrappedList.Add(tempProductCounter);
                OnStockChanged(new StockCollectionArgs(tempProductCounter));   // Starter vores OnStockChanged event med Argument.
            }
        }

        internal bool Remove(Product item)
        {
            return Remove(item, 1);                                    // Genbruger Remove metoden der modtager Product og Antal.
        }

        internal bool Remove(Product item, int number)
        {
            bool productExisted = false;                               // Bruges til at tjekke om der blev fjernet Product'er fra List'en. Bruges også som returværdi.
            List<int> toDelete = new List<int>();                      // List'e til midlertidigt at holde på Index-værdier.

            foreach (ProductCounter p in _wrappedList)
            {
                if (p.Product.Equals(item))                            // Bruger vores Equals override metode til at teste for værdi-mæssig lighed.
                {
                    if (p.NumberOfProduct > number)                    // Hvis der er flere Product'er end vi vil fjerne.
                    {
                        p.NumberOfProduct -= number;
                        productExisted = true;
                        OnStockChanged(new StockCollectionArgs(p));    // Starter vores OnStockChanged event med Argument.
                    }
                    else if (p.NumberOfProduct == number)              // Hvis mængden af et Product vi vil fjerne er lig med mængden i List'en.
                    {
                        toDelete.Add(_wrappedList.FindIndex(q => q.Product == item));   // Gemmer Index-værdi på det element vi vil fjerne fra List'en. (Kan ikke gøres i foreach-løkken)
                        productExisted = true;
                        OnStockChanged(new StockCollectionArgs(new ProductCounter(p.Product, 0)));  // Starter vores OnStockChanged event med Argument.
                    }
                }
                else
                {
                    throw new ArgumentOutOfRangeException("NumberOfProduct", "Vare-mængden eksisterer ikke på lageret.");      // Hvis man prøver at fjerne flere af et Product end der eksisterer eller 
                }                                                                                           // et Product der ikke er i List'en.

            }
            foreach (int k in toDelete)         // foreach-løkke der rydder op i listen og fjerner Product's med angivet Index-værdi.
            {
                _wrappedList.RemoveAt(k);
            }
            return productExisted;
        }
        #endregion

        #region Print-funktioner
        public void PrintStockList()
        {
            foreach (ProductCounter p in _wrappedList)                                  // Udskriver alle Product's og deres antal i konsollen.
                Console.WriteLine(p.ToString());
        }

        private void PrintSpecificList(List<ProductCounter> listToPrint)                // Metode der tager en List'e som indput som den udskriver.
        {
            foreach (ProductCounter p in listToPrint)
                Console.WriteLine(p.ToString());
        }
        #endregion

        #region Enumerators
        public IEnumerator GetEnumerator()                                      // Returnerer et Product af gangen som en IEnumerator. 
        {
            for (int i = 0; i < _wrappedList.Count; i++)
                yield return _wrappedList[i];                                   // "Yield return" gør at metoden returnerer 1 værdi og husker hvor den er kommet til.
        }                                                                       // Næste gang metoden kaldes returnerer metoden det næste Product og mængde. 

        public IEnumerable GetEnumeratorPriceDescending()
        {
            IEnumerable<ProductCounter> PriceDescending = _wrappedList          // Laver en IEnumerable collection til en midlertidig liste af vores Product's.
                .OrderByDescending(x => x.Product.Price)                        // Sorterer listen med faldende værdier (dyreste Product først).
                .ThenBy(x => x.Product.Name);                                   // Derefter sorteres alfabetisk.
            for (int i = 0; i < _wrappedList.Count; i++)
                yield return PriceDescending.ElementAt(i);                      // Til sidst returneres 1 værdi og der huskes hvor den er kommet til.
        }

        public IEnumerable GetEnumeratorPriceAscending()                        // Gør det samme som GetEnumeratorPriceDescending() men kører den midlertidige 
        {                                                                       // IEnumerable collection igennem bagfra.
            IEnumerable<ProductCounter> PriceDescending = _wrappedList
                .OrderByDescending(x => x.Product.Price)
                .ThenBy(x => x.Product.Name);
            for (int i = _wrappedList.Count - 1; i >= 0; i--)                   // Her køres PriceDescending igennem bagfra.
                yield return PriceDescending.ElementAt(i);
        }
        #endregion

        #region Søge-metoder
        // Der laves en midlertidig List til at gemme matchende Product's i. Og Product.Name undersøges mod indput i en foreach-løkke. 
        public List<ProductCounter> FindMatchingProductsByName(string nameToSearchFor, bool wantToPrint)
        {
            List<ProductCounter> tempMatchingProductsByName = new List<ProductCounter>();   // Midlertidig List'e.

            foreach (ProductCounter p in _wrappedList)
            {
                if (p.Product.Name == nameToSearchFor)                                      // Check om indput passer med Product.Navn fra lageret.
                    tempMatchingProductsByName.Add(p);
            }
            if (wantToPrint)                                                                // Den midlertidige List udskrives hvis indput parameteren wantToPrint er sat til 'true'.
                PrintSpecificList(tempMatchingProductsByName);

            return tempMatchingProductsByName;                                              // Den midlertidige liste returneres.
        }

        // FindProductByRange tager et object af vores Range struct som indput, der indeholder en min og en max værdi.
        public List<ProductCounter> FindProductByPriceRange(Range range, bool wantToPrint)
        {
            List<ProductCounter> tempProductByPriceRange = new List<ProductCounter>();      // Midlertidig List'e.

            foreach (ProductCounter p in _wrappedList)
            {
                if (range.IsOrBetween(p.Product.Price))                                     // Test med foreach-løkke om Product p's pris ligger inden for range med  
                    tempProductByPriceRange.Add(p);                                         // IsOrBetween metoden fra Range struct'en.               
            }

            if (wantToPrint)                                                                // Den midlertidige List udskrives hvis indput parameteren wantToPrint er sat til 'true'.
                PrintSpecificList(tempProductByPriceRange);

            return tempProductByPriceRange;                                                 // Den midlertidige liste returneres.
        }

        // Finder alle Dryers (tørretrumblere) med den energi klasse der gives som indput.
        public List<ProductCounter> FindDryerByEnergyClass(EEnergyClass energyClass, bool wantToPrint)
        {
            List<ProductCounter> tempProductByEnergyClass = new List<ProductCounter>();     // Midlertidig List'e.

            foreach (ProductCounter p in _wrappedList)
            {
                if (p.Product is Dryer)                                                     // Tjek om objectet er et Dryer object eller arver fra Dryer.
                {
                    Dryer tempDryer = p.Product as Dryer;                                   // Laves et cast af p.Product til et Dryer object hvorefter
                    if (tempDryer.EnergyClass == energyClass)                               // energi klassen checkes mod indput.
                        tempProductByEnergyClass.Add(p);
                }
            }

            if (wantToPrint)                                                // Den midlertidige List udskrives hvis indput parameteren wantToPrint er sat til 'true'.
                PrintSpecificList(tempProductByEnergyClass);

            return tempProductByEnergyClass;                                // Den midlertidige liste returneres.
        }

        // Finder først Product'er der implementerer ISmileyRating Interface'et og tjekker dernæst Smiley værdien mod indput hvis den gjorde.
        // For at få fat i hvert Product's smiley rating kaldes GetSmiley() metoden der blev erklæret i Interface'et.
        public List<ProductCounter> FindProductBySmileyRating(ESmiley smileyRating, bool wantToPrint)
        {
            List<ProductCounter> tempProductBySmileyRating = new List<ProductCounter>();     // Midlertidig List'e.

            foreach (ProductCounter p in _wrappedList)
            {
                ISmileyRating smileyTest = p.Product as ISmileyRating;             // p.Product cast'es om til et ISmileyRating Interface element. 

                if (smileyTest != null && smileyTest.GetSmiley() == smileyRating)  // Tjek om objectet har en null værdi (havde ikke ISmileyRating implementeret) og 
                    tempProductBySmileyRating.Add(p);                              // sammenligner derefter Smiley værdien med indput parameteren.
            }

            if (wantToPrint)                                                      // Den midlertidige List udskrives hvis indput parameteren wantToPrint er sat til 'true'.
                PrintSpecificList(tempProductBySmileyRating);

            return tempProductBySmileyRating;                                     // Den midlertidige liste returneres.
        }

        #endregion

        #region Events
        // Test om der er nogen abbonenter til vores EventHandler OnStockChange, hvis der er startes et event med argument.
        internal virtual void OnStockChanged(StockCollectionArgs e)
        {
            if (OnStockChange != null)
                OnStockChange(this, e);
        }
        #endregion
        #endregion

    }
}
